import time
from threading import RLock
from typing import Dict, Hashable, Union, Any, Optional

T_TTL = Union[int, float]
UNSET = object()  # 用来标记为缓存没有设置, 因为有些缓存可能故意设置为None

class MemoryCache(object):

    _cache: Dict
    _expire_times: Dict[Hashable, T_TTL]
    _lock: RLock

    def __init__(self, ttl=None):

        self._ttl = ttl
        self.timer = time.time  # 默认用来计算过期时间的函数

        self._cache = {}
        self._expire_times = {}
        self._lock = RLock()

    def has(self, key: Hashable) -> bool:
        with self._lock:
            value = self._get(key)
            return value is not UNSET

    def get(self, key: Hashable, default=None) -> Any:
        """获取键所对应的值, 如果不存在, 默认会返回None或者设置的值"""
        with self._lock:
            value = self._get(key)
            if value is UNSET:
                return default
            return value

    def _get(self, key: Hashable) -> Any:
        try:
            value = self._cache[key]
            if self.expired(key):
                self._delete(key)
                value = UNSET
        except KeyError:
            value = UNSET
        return value

    def expired(self, key: Hashable, expires_on: Optional[T_TTL] = None) -> bool:
        """
        判断给定的key是否过期
        :param key:
        :param expires_on: 过期时间, 不指定, 默认当前时间
        :return:
        """
        if not expires_on:
            expires_on = self.timer()
        try:
            return self._expire_times[key] <= expires_on
        except KeyError:
            return key not in self._cache

    def set(self, key: Hashable, value: Any, ttl: Optional[T_TTL] = None):
        """设置缓存, 如果不指定ttl, 那么使用初始化时指定的默认ttl, 如果ttl为空, 那么默认缓存不会过期"""
        with self._lock:
            self._set(key, value, ttl)

    def _set(self, key: Hashable, value: Any, ttl: Optional[T_TTL] = None):
        if ttl is None:
            ttl = self._ttl

        if key not in self._cache:
            self.evict_policy()

        self._delete(key)
        self._cache[key] = value

        if ttl and ttl > 0:
            self._expire_times[key] = self.timer() + ttl

    def evict_policy(self) -> int:
        """缓存淘汰策略"""
        count = self.delete_expired()
        # 这里不考虑其他情况了, 假设缓存数量无限制
        return count
    def delete_expired(self):
        """删除淘汰已经过期的key"""
        with self._lock:
            return self._delete_expired()
    def _delete_expired(self) -> int:
        if not self._expire_times:
            return 0

        count = 0
        expires_on = self.timer()
        expire_times = self._expire_times.copy()

        for key, expiration in expire_times.items():
            if expiration <= expires_on:
                count += self._delete(key)
        return count

    def delete(self, key: Hashable) -> int:
        """
        :param key: 缓存中的key,
        :return: 如果存在, 返回1, 否则返回0
        """
        with self._lock:
            return self._delete(key)

    def _delete(self, key: Hashable) -> int:
        count = 0
        try:
            del self._cache[key]
            count = 1
        except KeyError:
            pass
        try:
            del self._expire_times[key]
        except KeyError:
            pass

        return count

    def ttl(self, key) -> int:
        """获取某个键的剩余时间, 如果键不存在-2, 没有过期时间-1， 其他剩余的时间(单位: s)"""
        if key in self._cache:
            if key in self._expire_times:
                expires_on = self.timer()
                expiration = self._expire_times[key]
                delta_t = int(expiration - expires_on)
                if delta_t < 0:
                    self._delete(key)
                    return -2
                return delta_t
            else:
                return -1
        return -2


# 默认实例化的对象, 可以导入这个单例对象进行设置缓存
CacheUtils = MemoryCache()


if __name__ == '__main__':

    def test_utils():
        CacheUtils.set('a', 1, 2)
        CacheUtils.set('b', 3)
        v = CacheUtils.get('a')
        assert v == 1
        import time
        print(CacheUtils.ttl('a'))
        time.sleep(2)
        v = CacheUtils.get('a')
        v2 = CacheUtils.get('b')
        assert v is None
        assert v2 == 3
        CacheUtils.delete('b')
        assert CacheUtils.get('b') is None

        CacheUtils.set('a', None)
        assert CacheUtils.has('a') == True
        assert CacheUtils.get('a') is None
        CacheUtils.delete('a')
        assert CacheUtils.has('a') == False
        assert CacheUtils.expired('a') == True

        print(CacheUtils.ttl('a'))

        print('test pass')


    test_utils()